Google-zx  是谷歌推出的一个开源的项目, 一个可以使用前端熟悉的JavaScript语法来编写 shell 的工具。如:
| 1 | await $`cat package.json | grep name` | 
上面代码中可以看出在 JavaScript 中 插入了 shell。那么这种写法行不行呢? 答案是可以的,如果我们使用了Google-zx  的话。
问题背景
创建 shell 脚本的主要目的是 shell 脚本能够帮忙我们自动化实现一些重复的任务的。在编写脚本的时候通常会选择一些更加方便的编程预言。 对于前端开发工程师来说 Node.js 无疑是最好的选择了,它提供了很多核心的模块,并且还可以导入前端其他的脚本库。可以降低很多的学习成本。
但是我们尝试编写一个在Node.js下运行的 shell 脚本,发现并不是很流畅。 需要为子进程编写特殊处理;注意转义命令行参数;然后使用使用标准输出   stdout 和错误 stderr  ,显得不是很直观; 并且在使用之前需要做很多额外的操作,如:引入库等。
| 1 | // 引入 execSync 命令 from child_process 模块 | 
| 1 | // 引入 exec 命令 from child_process 模块 | 
Bash shell 脚本语言试试编写shell脚本的最佳选择,不需要编写代码来处理子进程,并且它有用于处理
stdout 和 stderr 的内置语言特性。 但是Bash 编写 shell 脚本也不是那么容易。语法比较混乱,使得实现逻辑或处理用户提示是输入之类的事情变得不是那么方便。
那么Google的zx.js库就很有助于使用Node.js高效快速的编写shell脚本。
安装
可根据如下命令进行全局安装:
| 1 | npm i -g zx | 
不一定非得全局安装, 可以局部安装的。下面的例子,都是使用的全局安装
目前需要的环境
| 1 | Node.js >= 16.0.0 | 
具体的
Node环境最新可到 Google/zx查看。
使用
安装好 zx 之后,有两种方式可以编写脚本:
- 将 shell脚本编写在后缀名为.mjs的文件中, 不需要额外的封装了
- 将 shell脚本编写在后缀名为.js的文件中,这种方式需要使用如下方式对脚本进行封装
| 1 | void async function() { | 
其次需要在文件头添加如下脚本:
| 1 | 
运行的时候如果是全局安装的情况下直接在命令行使用zx <文件>即可
| 1 | $ zx ./src/index.mjs | 
举个例子:
| 1 | 
 | 
 
内置函数
$`command`
该命令主要是使用了 child_process 的 spawn ( child_process.spawn ) 函数来执行指定的字符串,并返回一个ProcessPromise。如果执行的程序返回非零退出代码。 将抛出ProcessOutput。
举个例子:
| 1 | // 命令 | 
ProcessPromise
返回的 ProcessPromise 的 typescript 接口定义为:
| 1 | class ProcessPromise<T> extends Promise<T> { | 
其中pipe()方法可用于重定向标准输出:
| 1 | await $`echo "你好世界"` | 
结果如下:
 
关于更多的管道例子
ProcessOutput
如果执行的程序返回非零退出代码,ProcessOutput 将被抛出。ProcessPromise的typescript定义为:
| 1 | class ProcessPromise<T> extends Promise<T> { | 
举个例子:
| 1 | try { | 
结果:
 
cd()
cd() 可用于更改当前工作目录:
| 1 | // 更改当前的工作目录为: src 目录 | 
结果:
 
fetch()
相当于 node 的  node-fetch 包。 用于网络请求。
| 1 | let resp = await fetch('https://www.fastmock.site/mock/31e59cb8bdba6b63482fd4e6914b76f2/borrow/getName') | 
结果:
 
question()
是对 Node Js 的  readline  包的包装。它的作用是可以提示用户输入。
| 1 | let name = await question('请输入你的名字: ') | 
 
说是 question 函数的第二个参数是选项。但是没有试出来, 还没找到原因。
sleep()
使用 setTimeout 实现的一个等待函数。
| 1 | let name = await question('请输入你的名字: ') | 
nothrow()
捕捉 $ 执行命令时遇到的非 0 返回值,使其不抛异常。一般来说,Shell 编程中Exit Code不为0代表有异常
| 1 | try { | 
举个例子:
该例子中,开始没有使用 nothrow 函数,执行命令行遇到非0返回值是会抛出错误的。 之后使用 nothrow 函数, 执行,执行命令行遇到非0返回值则不会抛出错误。
 
内置的全局变量
chalk
 即 chalk 包,用于输出彩色的内容
| 1 | console.log(chalk.green('绿色')); | 
 
更多的使用可看: chalk
fs
引用的 fs-extra 包,用于完成常见的文件操作。
| 1 | fs.copy('./copy.txt', 'copy1.txt') | 
结果:
 
例子中复制了名为 copy.txt 文件, 复制后的文件名为:copy1.txt。
globby
引用的 globby 包,用于模糊搜索文件名
| 1 | const paths = await globby(['*', '!cake']); | 
os
引用的 os 包,用于获取系统信息
| 1 | const type = os.type(); | 
path
引用的 path 包,用于对路径做处理。
| 1 | const file = path.basename("/foo/bar/baz/asdf/shuliqi.txt") | 
minimist
引用的 minimist 包,用于处理命令行参数。
